﻿#region

using System.IdentityModel.Tokens.Jwt;
using System.Reflection;
using System.Security.Claims;
using System.Text;
using Microsoft.IdentityModel.Tokens;
using Vin.Extension.Cache;
using Vin.Extension.JwtBearer.Attributes;
using Vin.Extension.JwtBearer.Constants;
using Vin.Extension.JwtBearer.Extension;
using Vin.Extension.JwtBearer.Models;
using Vin.Extension.Redis.Cache;
using Vin.Tool.AspNetCore.Singleton;
using Vin.Tool.Core.AssertCategory;
using Vin.Tool.Core.ConvertCategory;
using Vin.Tool.Core.DateTimeCategory;
using Vin.Tool.Core.JsonCategory;
using Vin.Tool.Core.NetCategory;
using Vin.Tool.Domain.ExceptionModel;

#endregion

namespace Vin.Extension.JwtBearer.Tool;

public class VinSecurityTool
{
    public static JwtOption JwtOption => VinJwtExtension.JwtOption ?? throw new BusinessException("终端类型不能为空");

    public static string TokenKey(string? prefix = null)
    {
        prefix ??= VinApp.HttpContextOrNull?.GetPrefix();
        return prefix.IsNullOrEmpty()
            ? JwtOption.TokenKey
            : JwtOption.TokenKey + (prefix!.EndsWith(":")
                ? prefix
                : prefix + ":");
    }

    public static string TerminalKey(string? prefix = null)
    {
        prefix ??= VinApp.HttpContextOrNull?.GetPrefix();
        return prefix.IsNullOrEmpty()
            ? JwtOption.TerminalKey
            : JwtOption.TerminalKey + (prefix!.EndsWith(":")
                ? prefix
                : prefix + ":");
    }

    public static string CreateJwtToken(List<Claim> claims, string secretKey, int? expireTime = null,
        string? issuer = null, string? audience = null, DateTime? notBefore = null)
    {
        expireTime ??= JwtOption.TokenExpireTime;
        notBefore ??= DateTime.UtcNow;

        var signingAlgorithm = SecurityAlgorithms.HmacSha256;
        // 用户信息
        // claims
        // 获取私钥 并加密
        var signingKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secretKey));
        // 生成数字签名
        var signingCredentials = new SigningCredentials(signingKey, signingAlgorithm);

        var header = new JwtHeader(signingCredentials);
        var payload = new JwtPayload(issuer, audience, claims, notBefore, DateTime.UtcNow.AddMinutes(expireTime.Value));

        return new JwtSecurityTokenHandler().WriteToken(new JwtSecurityToken(header, payload));
    }

    /// <summary>
    /// 只创建JwtToken
    /// </summary>
    /// <param name="claims"></param>
    /// <returns></returns>
    public static string CreateJwtToken(List<Claim> claims)
    {
        return CreateJwtToken(claims, JwtOption.SecretKey);
    }

    private static async Task CheckNewTerminal<T>(T tokenModel, string? prefix = null, TimeSpan? expireTime = null)
        where T : VinJwtTokenModel
    {
        var redisCache = RedisCacheManager.GetRedisCache();
        var terminalKey = TerminalKey(prefix) + tokenModel.RealAccountId + ":" + tokenModel.TerminalType;
        var timestamp = DateTime.Now.ToTimestamp();
        var totalMilliseconds = expireTime?.TotalMilliseconds.ToLong() ?? 0;
        await redisCache.LuaResourceFileAsync(typeof(VinSecurityTool).Assembly, "Lua.LoginTerminal.lua",
            new() { terminalKey },
            timestamp, JwtOption.TerminalCount, TokenKey(prefix), tokenModel.UserId!, totalMilliseconds,
            tokenModel.ToCamelCaseJson());

        #region C# 伪代码

        // var redisCache = RedisCacheManager.GetRedisCache();
        // var key = TerminalKey(prefix) + tokenModel.RealAccountId + ":" + tokenModel.TerminalType;
        // // 获取已经过期的终端
        // var timestamp = DateTime.Now.ToTimestamp();
        // var terminalUserIds = await redisCache.GetScoreCacheByScoreAsync(key, 0, timestamp);
        // // 清理过期的终端
        // if (terminalUserIds.Count > 0)
        // {
        //     await redisCache.RemoveCacheAsync(terminalUserIds.Select(m => TokenKey(prefix) + m).ToArray());
        //     await redisCache.RemoveScoreCacheByScoreAsync(key, 0, timestamp);
        // }
        //
        // // 获取终端数量
        // var count = await redisCache.MemberCountAsync(key);
        // if (count >= JwtOption.TerminalCount)
        // {
        //     var first = (await redisCache.GetScoreCacheByIndexAsync(key, 0, 0)).FirstOrDefault();
        //     if (first != null)
        //     {
        //         // 清理之前的token
        //         await redisCache.RemoveCacheAsync(TokenKey(prefix) + first);
        //         await redisCache.RemoveScoreCacheAsync(key, first);
        //     }
        // }
        //
        // // 存入终端对应关系
        // var totalMilliseconds = expireTime?.TotalMilliseconds.ToLong() ?? 0;
        // await redisCache.SetScoreCacheAsync(key, tokenModel.UserId!, timestamp + totalMilliseconds);

        #endregion
    }

    private static async Task UpdateNewTerminal<T>(T tokenModel, string? prefix = null, TimeSpan? expireTime = null)
        where T : VinJwtTokenModel
    {
        var key = TerminalKey(prefix) + tokenModel.RealAccountId + ":" + tokenModel.TerminalType;
        // 更新终端对应分数
        await RedisCacheManager.GetRedisCache()
            .SetScoreCacheAsync(key, tokenModel.UserId!, (DateTime.Now + expireTime).ToTimestamp());
    }

    /// <summary>
    /// 开启检测终端
    /// </summary>
    /// <param name="tokenModel"></param>
    /// <param name="prefix"></param>
    /// <param name="expireTime"></param>
    /// <typeparam name="T"></typeparam>
    /// <exception cref="BusinessException"></exception>
    private static void CheckTerminal<T>(T tokenModel, string? prefix = null, TimeSpan? expireTime = null)
        where T : VinJwtTokenModel
    {
        // 如果开启终端
        if (JwtOption.OpenTerminal)
        {
            if (tokenModel.TerminalType.IsNullOrEmpty()) throw new BusinessException("终端类型不能为空");
            // 获取终端对应关系
            var terminalKey = TerminalKey(prefix) + tokenModel.RealAccountId;
            var terminalUserId = CacheFactory.GetHashCache<string>(terminalKey, tokenModel.TerminalType!);
            if (terminalUserId.IsNotNullOrEmpty())
            {
                // 清理之前的token
                CacheFactory.RemoveCache(TokenKey(prefix) + terminalUserId);
            }

            // 存入终端对应关系
            CacheFactory.SetHashCache(terminalKey, tokenModel.TerminalType!, tokenModel.UserId, expireTime);
        }
    }

    /// <summary>
    /// 创建授权Token
    /// </summary>
    public static string CreateAuthorizeToken<T>(T tokenModel, string? prefix = null, int? expireTime = null,
        bool? closeTerminal = null)
        where T : VinJwtTokenModel
    {
        tokenModel.UserId ??= Guid.NewGuid().ToString("N");
        tokenModel.Prefix = prefix;

        var clientInfo = VinApp.HttpContext.GetClientDetail(true);
        tokenModel.Ip = clientInfo.Ip;
        tokenModel.Location = clientInfo.Location;
        tokenModel.Browser = clientInfo.Browser;
        tokenModel.Os = clientInfo.Os;
        tokenModel.UserAgent = clientInfo.UserAgent;
        tokenModel.Device = clientInfo.Device;
        tokenModel.LoginTime = DateTime.Now;

        // 过期时间
        var expireTimeSpan = TimeSpan.FromMinutes(expireTime ?? JwtOption.TokenExpireTime);

        // 创建token
        var token = CreateJwtToken(AddGenClaims(tokenModel));

        // 避免并发问题
        // lock (_lock)
        // {
        //     // 检查终端
        //     CheckTerminal(tokenModel, prefix, expireTimeSpan);
        if (!JwtOption.OpenTerminal || closeTerminal == true)
        {
            // 存入token放缓存
            CacheFactory.SetCache(TokenKey(prefix) + tokenModel.UserId, tokenModel, expireTimeSpan);
        }
        else
        {
            CheckNewTerminal(tokenModel, prefix, expireTimeSpan).GetAwaiter().GetResult();
        }
        // if (JwtOption.OpenTerminal)
        // {
        //     CheckNewTerminal(tokenModel, prefix, expireTimeSpan).GetAwaiter().GetResult();
        // }
        // else
        // {
        //     // 存入token放缓存
        //     CacheFactory.SetCache(TokenKey(prefix) + tokenModel.UserId, tokenModel, expireTimeSpan);
        // }

        // }

        return token;
    }

    /// <summary>
    /// 更新授权Token的信息
    /// </summary>
    public static void UpdateAuthorizeToken<T>(T newModel, string? prefix = null, int? expireTime = null,
        bool? closeTerminal = null)
        where T : VinJwtTokenModel
    {
        var oldModel = GetTokenModelByToken<T>(prefix);
        EmptyTool.CheckIsNull(oldModel, "未授权");

        AddGenClaims(newModel);

        // 过期时间
        var expireTimeSpan = TimeSpan.FromMinutes(expireTime ?? JwtOption.TokenExpireTime);

        // lock (_lock)
        // {
        //     // 检查终端
        //     CheckTerminal(newModel, prefix, expireTimeSpan);

        if (closeTerminal == true || !JwtOption.OpenTerminal)
        {
        }
        else
        {
            UpdateNewTerminal(newModel, prefix, expireTimeSpan).GetAwaiter().GetResult();
        }

        // 更新放缓存
        CacheFactory.SetCache(TokenKey(prefix) + newModel.UserId, newModel, expireTimeSpan);

        // }
    }

    /// <summary>
    /// 删除授权Token
    /// </summary>
    /// <exception cref="Exception"></exception>
    public static void LogoutAuthorizeToken(string? prefix = null)
    {
        var tokenModel = GetTokenModelByToken();
        if (tokenModel == null) return;

        CacheFactory.RemoveCache(TokenKey(prefix) + tokenModel.UserId);
        if (JwtOption.OpenTerminal)
        {
            var terminalKey = TerminalKey(prefix) + tokenModel.RealAccountId;
            CacheFactory.RemoveHashCache(terminalKey, tokenModel.TerminalType!);
            if (CacheFactory.GetHashCount(terminalKey) <= 0)
            {
                CacheFactory.RemoveCache(terminalKey);
            }
        }
    }

    public static void LogoutAuthorizeToken(string userId, string? prefix)
    {
        var tokenModel = CacheFactory.GetCache<VinJwtTokenModel>(TokenKey(prefix) + userId);
        if (tokenModel == null) return;

        CacheFactory.RemoveCache(TokenKey(prefix) + tokenModel.UserId);
        if (JwtOption.OpenTerminal)
        {
            var terminalKey = TerminalKey(prefix) + tokenModel.RealAccountId;
            CacheFactory.RemoveHashCache(terminalKey, tokenModel.TerminalType!);
            if (CacheFactory.GetHashCount(terminalKey) <= 0)
            {
                CacheFactory.RemoveCache(terminalKey);
            }
        }
    }

    public static void LogutAuthorizeTokenByIdTerminal(long id, string terminalType, string? prefix = null)
    {
        var terminalKey = TerminalKey(prefix) + id + ":" + terminalType;
        var userIds = RedisCacheManager.GetRedisCache().GetScoreCacheByIndexAsync(terminalKey, 0, long.MaxValue).Result;
        foreach (var userId in userIds)
        {
            CacheFactory.RemoveCache(TokenKey(prefix) + userId);
        }

        CacheFactory.RemoveCache(terminalKey);
    }

    /// <summary>
    /// 获取授权Token的信息
    /// </summary>
    /// <returns></returns>
    public static string? GetTokenModelStrByToken(string? prefix = null)
    {
        var userData = VinApp.User?.Claims.FirstOrDefault(m => m.Type == ClaimTypes.UserData)?.Value;
        if (userData.IsNotNullOrEmpty()) return userData;

        var userId = VinApp.User?.Claims.FirstOrDefault(m => m.Type == VinClaimTypesConstant.UserId)?.Value;
        if (userId.IsNullOrEmpty()) return null;

        userData = CacheFactory.GetCache<string>(TokenKey(prefix) + userId);
        if (userData.IsNullOrEmpty()) return null;

        VinApp.HttpContext.AddClaimsToHttpContext(ClaimTypes.UserData, userData);
        // var model = userData?.ToCamelCaseObject<VinJwtTokenModel>();
        // if (model != null)
        // {
        //     VinApp.HttpContext.AddClaimsToHttpContext(model.Claims);
        // }
        return userData;
    }

    public static T? GetTokenModelByToken<T>(string? prefix = null) where T : VinJwtTokenModel =>
        GetTokenModelStrByToken(prefix)?.ToCamelCaseObject<T>();

    public static object? GetTokenModelByToken(Type type, string? prefix = null) =>
        GetTokenModelStrByToken(prefix)?.ToCamelCaseObject(type);

    public static VinJwtTokenModel? GetTokenModelByToken(string? prefix = null) =>
        GetTokenModelByToken<VinJwtTokenModel>(prefix);

    /// <summary>
    /// 添加自定义的Claims
    /// </summary>
    /// <param name="model"></param>
    /// <typeparam name="T"></typeparam>
    /// <returns></returns>
    public static List<Claim> AddGenClaims<T>(T model) where T : VinJwtTokenModel
    {
        var properties = typeof(T).GetProperties();

        var joinClaims = new List<Claim>();
        model.Claims ??= new Dictionary<string, string>();

        foreach (var property in properties)
        {
            var claimType = property.GetCustomAttribute<VinJwtClaimTypeAttribute>();
            if (claimType == null) continue;
            var value = property.GetValue(model);
            if (value == null) continue;

            if (claimType is not { JoinInJwt: true })
            {
                if (model.Claims.ContainsKey(claimType.Name))
                {
                    // model.Claims[property.Name] = claimType.IsObject ? value.ToJson() : value.ToString()!;
                    model.Claims[claimType.Name] = claimType.IsObject ? value.ToCamelCaseJson() : value.ToString()!;
                }
                else
                {
                    // model.Claims.Add(property.Name, claimType.IsObject ? value.ToJson() : value.ToString()!);
                    model.Claims.Add(claimType.Name, claimType.IsObject ? value.ToCamelCaseJson() : value.ToString()!);
                }

                continue;
            }

            joinClaims.Add(claimType.IsObject
                ? new Claim(claimType.Name, value.ToCamelCaseJson())
                : new Claim(claimType.Name, value.ToString()!));
        }

        return joinClaims;
    }

    #region RefreshToken

    public static string CreateRefreshToken<T>(T tokenModel, int? expireTime = null)
        where T : VinJwtTokenModel
    {
        tokenModel.UserId ??= Guid.NewGuid().ToString("N");
        expireTime ??= JwtOption.RefreshTokenExpireTime;
        var token = CreateJwtToken(AddGenClaims(tokenModel), JwtOption.RefreshTokenSecretKey ?? JwtOption.SecretKey,
            expireTime.Value);
        // 存入缓存
        CacheFactory.SetCache(JwtOption.RefreshTokenKey + tokenModel.UserId, tokenModel,
            TimeSpan.FromMinutes(expireTime.Value));
        return token;
    }

    public static T GetTokenModelByRefreshToken<T>(string refreshToken)
        where T : VinJwtTokenModel, new()
    {
        var jwtHandler = new JwtSecurityTokenHandler();
        try
        {
            var jwtToken = jwtHandler.ReadJwtToken(refreshToken);
            if (jwtToken == null) throw new BusinessException("无效的RefreshToken");
            var userId = jwtToken.Claims.FirstOrDefault(m => m.Type == VinClaimTypesConstant.UserId)?.Value;
            if (userId.IsNullOrEmpty()) throw new BusinessException("无效的RefreshToken");
            var tokenModel = CacheFactory.GetCache<T>(JwtOption.RefreshTokenKey + userId);
            if (tokenModel == null) throw new BusinessException("无效的RefreshToken");
            return tokenModel;
        }
        catch (Exception)
        {
            throw new BusinessException("无效的RefreshToken");
        }
    }

    public static void RemoveRefreshToken(string? userId)
    {
        if (userId.IsNotNullOrEmpty())
        {
            CacheFactory.RemoveCache(JwtOption.RefreshTokenKey + userId);
        }
    }

    #endregion
}